/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package com.thienloc.omap.controller.report;

import com.google.common.base.Splitter;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.thienloc.omap.controller.BaseBean;
import com.thienloc.omap.entity.TinhThanh;
import com.thienloc.omap.entity.report.MoStatistic;
import com.thienloc.omap.jdbc.dao.TblSmsBroadcastingDao;
import com.thienloc.omap.session.SmsRegexServiceMappingFacade;
import com.thienloc.omap.session.TinhThanhFacade;
import com.thienloc.omap.util.Constant;
import com.thienloc.omap.util.JsfUtil;
import java.io.FileReader;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.ejb.EJB;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 *
 * @author GiangLT
 */
@ManagedBean
@ViewScoped
public class MtProductionBean extends BaseBean implements Serializable {

    @EJB
    private TinhThanhFacade tinhThanhFacade;
    @EJB
    private SmsRegexServiceMappingFacade smsRegexServiceMappingFacade;
    private final Logger logger = LoggerFactory.getLogger(MtProductionBean.class);
    private ThreadPoolExecutor executor;
    public Boolean isProcessing = false;
    private static final int CORE_POOL_SIZE = 10;
    private static final int MAXIMUM_POOL_SIZE = 15;
    private List<String> selectedProviders;
    private List<String> unmatchSmses;
    private String unmatchSms;
    private final String BY_DAY = "byDay";
    private final String BY_MONTH = "byMonth";
    private String reportType = BY_MONTH;
    private String month;
    private String year;
    private Date day;
    private List<MoStatistic> mtProductions;
    private List<MoStatistic> mtSummaryProductions;

    @PostConstruct
    public void postConstruct() {
        executor = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
        Calendar cal = Calendar.getInstance();
        int thisMonth = cal.get(Calendar.MONTH) + 1;
        day = cal.getTime();
        month = getMonthOrDayString(thisMonth);
        year = String.valueOf(cal.get(Calendar.YEAR));
    }

    @PreDestroy
    public void preDestroy() {
        if (executor != null) {
            executor.shutdown(); // Disable new tasks from being submitted
            try {
                // Wait a while for existing tasks to terminate
                if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
                    executor.shutdownNow(); // Cancel currently executing tasks
                    // Wait a while for tasks to respond to being cancelled
                    if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
                        logger.error("Executor did not terminate");
                    }
                }
            } catch (InterruptedException ie) {
                // (Re-)Cancel if current thread also interrupted
                executor.shutdownNow();
//            // Preserve interrupt status
//            Thread.currentThread().interrupt();
            }
        }
    }

    //Đếm các tin nhắn, group by nội dung
    //Match tin nhắn để ra cú pháp từng tỉnh, VD S2 HCM
    //Từ cú pháp map ra cú pháp miền VD S2 HCM => S2 MN
    public void report() {
        try {
            //Đếm các tin nhắn, group by nội dung
            String dayStr = getMonthOrDayString(day.getDate());
            logger.trace("[{}] Counting all broadcasted messages", getUsername());
            TblSmsBroadcastingDao smsBroadcastingDao = new TblSmsBroadcastingDao();
            Map<String, Integer> smsBroadcastingMap;
            if (reportType.equals(BY_MONTH)) {
                smsBroadcastingMap = smsBroadcastingDao.find(month, year, selectedProviders);
            } else {
                smsBroadcastingMap = smsBroadcastingDao.find(dayStr, month, year, selectedProviders);
            }
            logger.trace("[{}] Total {} broadcasted messages", getUsername(), smsBroadcastingMap.size());

            //tạo map từ cú pháp tỉnh sang cú pháp miền
            List<TinhThanh> allProvinces = tinhThanhFacade.findAll();
            final Map<String, TinhThanh> provinceMap = Maps.newHashMapWithExpectedSize(allProvinces.size());
            for (TinhThanh province : allProvinces) {
                provinceMap.put(province.getMaTinh(), province);
            }

            //tạo map từ S1,S2,S3... về S
            Properties properties = new Properties();
            properties.load(new FileReader(Constant.CONFIG_FILE_PATH));
            Map<String, String> sameServiceSyntaxMap = Maps.newTreeMap();
            String[] primaryServiceSyntaxes = properties.getProperty("sameSyntax.primary", "S,A,T,N,G").split(",");
            for (String primaryServiceSyntax : primaryServiceSyntaxes) {
                String[] sameSyntaxes = properties.getProperty("sameSyntax." + primaryServiceSyntax).split(",");
                for (String sameSyntax : sameSyntaxes) {
                    sameServiceSyntaxMap.put(sameSyntax, primaryServiceSyntax);
                }
            }

            Map<String, Integer> provinceMtProductionMap = Maps.newTreeMap();
            //Tao map san luong MT, sap xep theo thu tu dinh san
            Splitter splitter = Splitter.on(',').trimResults().omitEmptyStrings();
            final List<String> syntaxOrders = Lists.newArrayList(splitter.split(properties.getProperty("moProductionSyntaxOrder", "")));
            Map<String, Integer> regionMtProductionMap = Maps.newTreeMap();
            for (String syntaxOrder : syntaxOrders) {
                regionMtProductionMap.put(syntaxOrder, 0);
            }

            //load nonLotteryServices
            List<String> nonLotteryServices = Lists.newArrayList(splitter.split(properties.getProperty("moProduction.nonLotteryServices", "TIP,TC,HUY")));

            //matching
            Set<String> broadcastedSmses = smsBroadcastingMap.keySet();
            unmatchSmses = Lists.newArrayList();

//            String regex = "soan[\\s\\S]*gui?[\\s\\S]*6771?";
            String regex = "soan[\\s]*[\\S]*[\\s]*[\\S]*";
            Pattern p = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
            Pattern p1 = Pattern.compile("soan[\\W]*", Pattern.CASE_INSENSITIVE);
            Pattern p2 = Pattern.compile("gui*[\\s\\S]*[6771]*", Pattern.CASE_INSENSITIVE);

            for (String broadcastedSms : broadcastedSmses) {
                try {

                    Integer broadcastedSmsProduction = smsBroadcastingMap.get(broadcastedSms);
                    logger.debug("Production: {}, SMS : {}", broadcastedSmsProduction, broadcastedSms);

                    Matcher matcher = p.matcher(broadcastedSms);
                    if (matcher.find()) {
                        String group = matcher.group(0);
                        logger.debug("Extracted phrase {}", group);

                        Matcher matcher1 = p1.matcher(group);
                        String replace1 = matcher1.replaceAll("").trim();
                        logger.debug("Deleted string1 {}", replace1);

                        Matcher matcher2 = p2.matcher(replace1);
                        String provinceSyntax = matcher2.replaceAll("").trim().toUpperCase();
                        logger.debug("Extracted province syntax: {}", provinceSyntax);

                        //add vào Map cú pháp tỉnh
                        Integer provinceMtProduction = provinceMtProductionMap.get(provinceSyntax);
                        if (provinceMtProduction == null) {
                            provinceMtProduction = 0;
                        }
                        provinceMtProductionMap.put(provinceSyntax, provinceMtProduction + broadcastedSmsProduction);

                        //đổi mã tỉnh sang mã vùng miền
                        List<String> tokens = Lists.newArrayList(Splitter.on(' ').trimResults().omitEmptyStrings().split(provinceSyntax));
                        String provinceCode;
                        if (tokens.size() == 1) {// S2, N2... ko có mã tỉnh => là MB
                            provinceCode = "MB";
                        } else {
                            provinceCode = tokens.get(1);
                        }

                        logger.debug("Province code: {}", provinceCode);
                        Integer regionCode = provinceMap.get(provinceCode).getRegionCode();
                        String regionSyntax = tokens.get(0);
                        String serviceSyntax = sameServiceSyntaxMap.get(regionSyntax);
                        if (serviceSyntax != null) {
                            regionSyntax = serviceSyntax;
                        }
                        switch (regionCode) {
                            case 0:
                                if (!nonLotteryServices.contains(regionSyntax)) {
                                    regionSyntax += " MB";
                                }
                                break;
                            case 1:
                                regionSyntax += " MT";
                                break;
                            default:
                                regionSyntax += " MN";
                        }

                        //add vào map cú pháp vùng
                        Integer regionMtProduction = regionMtProductionMap.get(regionSyntax);
                        if (regionMtProduction == null) {
                            regionMtProduction = 0;
                        }
                        regionMtProductionMap.put(regionSyntax, regionMtProduction + broadcastedSmsProduction);

                    } else {
                        logger.debug("No match found ({}):  {}", broadcastedSmsProduction, broadcastedSms);
                        unmatchSmses.add("Production: " + broadcastedSmsProduction);
                        unmatchSmses.add("Sms: " + broadcastedSms);
                        unmatchSmses.add("----------------------------------------------------------------");
                        unmatchSmses.add("");
                    }
                } catch (Exception ex) {
                    JsfUtil.addErrorMessage("Lỗi", ex.getMessage());
                    logger.error("", ex);
                }
            }

            mtProductions = createMtStatisticList(provinceMtProductionMap);
            mtSummaryProductions = createMtStatisticList(regionMtProductionMap);

            Comparator<MoStatistic> comparator = new Comparator<MoStatistic>() {

                @Override
                public int compare(MoStatistic o1, MoStatistic o2) {
                    return syntaxOrders.indexOf(o1.getContent()) - syntaxOrders.indexOf(o2.getContent());
                }
            };
            Collections.sort(mtSummaryProductions, comparator);
        } catch (Exception ex) {
            JsfUtil.addErrorMessage("Lỗi", ex.getMessage());
            logger.error("[{}] ", getUsername(), ex);
        }
    }

    private List<MoStatistic> createMtStatisticList(Map<String, Integer> provinceMtProductionMap) {
        Set<Map.Entry<String, Integer>> entrySet = provinceMtProductionMap.entrySet();
        List<MoStatistic> result = Lists.newArrayListWithCapacity(entrySet.size());
        for (Map.Entry<String, Integer> entry : entrySet) {
            MoStatistic statistic = new MoStatistic();
            statistic.setContent(entry.getKey());
            statistic.setCount(BigDecimal.valueOf(entry.getValue()));
            result.add(statistic);
        }
        return result;
    }

    private String getMonthOrDayString(int monthOrDay) {
        if (monthOrDay < 10) {
            return "0" + String.valueOf(monthOrDay);
        } else {
            return String.valueOf(monthOrDay);
        }
    }

    public String getMonth() {
        return month;
    }

    public void setMonth(String month) {
        this.month = month;
    }

    public String getYear() {
        return year;
    }

    public void setYear(String year) {
        this.year = year;
    }

    public String getReportType() {
        return reportType;
    }

    public void setReportType(String reportType) {
        this.reportType = reportType;
    }

    public String getBY_DAY() {
        return BY_DAY;
    }

    public String getBY_MONTH() {
        return BY_MONTH;
    }

    public Date getDay() {
        return day;
    }

    public void setDay(Date day) {
        this.day = day;
    }

    public List<String> getSelectedProviders() {
        return selectedProviders;
    }

    public void setSelectedProviders(List<String> selectedProviders) {
        this.selectedProviders = selectedProviders;
    }

    public Boolean getIsProcessing() {
        return isProcessing;
    }

    public void setIsProcessing(Boolean isProcessing) {
        this.isProcessing = isProcessing;
    }

    public String getUnmatchSms() {
        StringBuilder sb = new StringBuilder();
        for (String string : unmatchSmses) {
            sb.append(string);
        }
        unmatchSms = sb.toString();
        return unmatchSms;
    }

    public List<String> getUnmatchSmses() {
        return unmatchSmses;
    }

    public List<MoStatistic> getMtProductions() {
        return mtProductions;
    }

    public List<MoStatistic> getMtSummaryProductions() {
        return mtSummaryProductions;
    }

}
